/**
 * 
 */
package com.googlecode.xcache4j.transcoder;

import java.util.Date;

import net.rubyeye.xmemcached.transcoders.BaseSerializingTranscoder;
import net.rubyeye.xmemcached.transcoders.CachedData;
import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
import net.rubyeye.xmemcached.transcoders.Transcoder;
import net.rubyeye.xmemcached.transcoders.TranscoderUtils;

import org.apache.commons.lang3.SerializationException;

import com.googlecode.xcache4j.coder.Coder;
import com.googlecode.xcache4j.coder.HessianCoder;

/**
 * @author xy.du
 * 2015年1月11日
 */
public class HessianSerializingTranscoder extends BaseSerializingTranscoder implements Transcoder<Object> {
	
	
	private final int maxSize;
	private boolean primitiveAsString;
	
	static final int MAX_SIZE = 20*1024*1024;
	// General flags
	public static final int H_SERIALIZED = 5;
	public static final int DEFAULT_SERIALIZED = 1;
	public static final int COMPRESSED = 2;

	// Special flags for specially handled types.
	public static final int SPECIAL_MASK = 0xff00;
	public static final int SPECIAL_BOOLEAN = (1 << 8);
	public static final int SPECIAL_INT = (2 << 8);
	public static final int SPECIAL_LONG = (3 << 8);
	public static final int SPECIAL_DATE = (4 << 8);
	public static final int SPECIAL_BYTE = (5 << 8);
	public static final int SPECIAL_FLOAT = (6 << 8);
	public static final int SPECIAL_DOUBLE = (7 << 8);
	public static final int SPECIAL_BYTEARRAY = (8 << 8);

	private final TranscoderUtils transcoderUtils = new TranscoderUtils(true);
	private Coder coder;
	 
	public HessianSerializingTranscoder() {
		this(MAX_SIZE, new HessianCoder());
	}
 
	public HessianSerializingTranscoder(int max) {
		this.maxSize = max;
	}
 
	public HessianSerializingTranscoder(int max,Coder coder) {
		this.maxSize = max;
		this.coder = coder;
	}

	public final Object decode(CachedData d) {
		Object obj = d.decodedObject;
		if (obj != null) {
			return obj;
		}
		byte[] data = d.getData();

		int flags = d.getFlag();
		if ((flags & COMPRESSED) != 0) {
			data = decompress(data);
		} 
		flags = flags & SPECIAL_MASK;
		obj = decode0(d, data, flags);
		d.decodedObject = obj;
		return obj;
	}
	
	protected final Object decode0(CachedData cachedData,byte[] data, int flags) {
		Object rv = null;
		if((cachedData.getFlag() & H_SERIALIZED) == H_SERIALIZED && data != null){
			try {
				rv = this.coder.decode(data);
			} catch (Exception e) {
				throw new SerializationException("deserialize error by hessian ." , e);
			}
		}else if ((cachedData.getFlag() & 1) == 1 && data != null) {//compatible java serializ
			rv = deserialize(data);
		}else {
			if (this.primitiveAsString) {
				if (flags == 0) {
					return decodeString(data);
				}
			}
			if (flags != 0 && data != null) {
				switch (flags) {
				case SPECIAL_BOOLEAN:
					rv = Boolean.valueOf(this.transcoderUtils.decodeBoolean(data));
					break;
				case SPECIAL_INT:
					rv = Integer.valueOf(this.transcoderUtils.decodeInt(data));
					break;
				case SPECIAL_LONG:
					rv = Long.valueOf(this.transcoderUtils.decodeLong(data));
					break;
				case SPECIAL_BYTE:
					rv = Byte.valueOf(this.transcoderUtils.decodeByte(data));
					break;
				case SPECIAL_FLOAT:
					rv = new Float(Float.intBitsToFloat(this.transcoderUtils.decodeInt(data)));
					break;
				case SPECIAL_DOUBLE:
					rv = new Double(Double.longBitsToDouble(this.transcoderUtils.decodeLong(data)));
					break;
				case SPECIAL_DATE:
					rv = new Date(this.transcoderUtils.decodeLong(data));
					break;
				case SPECIAL_BYTEARRAY:
					rv = data;
					break;
				default:
					log.warn(String.format("Undecodeable with flags %x",flags));
				}
			} else {
				rv = decodeString(data);
			}
		}
		return rv;
	}

	public final CachedData encode(Object o) {
		byte[] b = null;
		try {
			b = this.coder.encode(o);
		} catch (Exception e) {
			throw new SerializationException("serilize object error by hessian", e);
		}
		
		int flags = H_SERIALIZED;
		assert b != null;
		if (b.length > this.compressionThreshold) {
			byte[] compressed = compress(b);
			if (compressed.length < b.length) {
				log.info("Compression increased the size of {} from {} to {}", new Object[]{o.getClass().getName(),b.length,compressed.length});
				b = compressed;
				flags |= COMPRESSED;
			} else {
				log.info("Compression increased the size of {} from {} to {}", new Object[]{o.getClass().getName(),b.length,compressed.length});
			}
		}
		return new CachedData(flags, b, this.maxSize, -1);
	}

	public void setPackZeros(boolean packZeros) {
		this.transcoderUtils.setPackZeros(packZeros);
	}

	public final int getMaxSize() {
		return this.maxSize;
	}

	public void setPrimitiveAsString(boolean primitiveAsString) {
		this.primitiveAsString = primitiveAsString;
	}
	
	public boolean isPackZeros() {
		return this.transcoderUtils.isPackZeros();
	}

	public boolean isPrimitiveAsString() {
		return this.primitiveAsString;
	}
	
	public TranscoderUtils getTranscoderUtils() {
		return transcoderUtils;
	}

	public Coder getCoder() {
		return coder;
	}

	public void setCoder(Coder coder) {
		this.coder = coder;
	}
	
	
}
